package ufpb.compiladores.sintatico.impl;

import org.w3c.dom.Element;
import org.w3c.dom.Document;
import ufpb.compiladores.lexico.LexicalTable.Entry;
import java.util.Stack;
import ufpb.compiladores.lexico.LexicalTable;
import ufpb.compiladores.sintatico.Clause;
import ufpb.compiladores.sintatico.Entity;
import ufpb.compiladores.sintatico.Enviroment;
import ufpb.compiladores.sintatico.Production;
import ufpb.compiladores.sintatico.SyntacticalAnalyzer;
import static ufpb.compiladores.sintatico.Entity.EntityType;
import ufpb.compiladores.sintatico.graph.SyntaticalTree;

/**
 *
 * @author SemiNick
 */
public class SyntacticalAnalyzerImpl implements SyntacticalAnalyzer {

    private final Enviroment env;
    private final LexicalTable lt;
    
    public SyntacticalAnalyzerImpl(Enviroment env, LexicalTable lt) {
    
       if (env == null) {
           
           throw new NullPointerException("Enviroment cannot be null");
       } else if (lt == null) {
           
           throw new NullPointerException("LexicalTable cannot be null");
       }
       
       this.env = env;
       this.lt = lt;
    }
    
    @Override
    public void processEnviroment(Document doc, Element r) {
        
        Stack<State> stackFrame = new Stack<State>();
        Production atual = this.env.getInitialProduction();
        int cp = 0;
        int k = 0;
        boolean ret_n_prod = true;
        boolean error = false;
        boolean errorC = false;
        String errorMsg = null;
        
        proc:
        while (atual != null) {
            
            /*if (!stackFrame.isEmpty()) {
            
                //cp = this.getClauseToProcess(atual, lt);
            //} else {
                
                atual = this.env.getProduction(stackFrame.peek().production);
                cp = stackFrame.peek().clause;
            }*/
            
            Clause[] ac = atual.getClauses();
            if (atual == this.env.getProduction("ativacao_de_procedimento")) {
                
                System.out.println("=D");
            }
            //errorC = false;
            nextc:
            for (; cp < ac.length; cp++) {
               
                errorC = false;
                Clause clauseP = ac[cp];
                Entity[] alle = clauseP.getEntities();
                for (; k < alle.length; k++) {
                
                    error = false;
                    if (!ret_n_prod) {
                        
                        if (stackFrame.isEmpty()) {
                            
                            System.out.println(errorMsg);
                        }
                        ret_n_prod = true;
                        k = 0;
                        continue nextc;
                    }
                    switch (alle[k].getType()) {
                        
                        case PRODUCTION:
                            
                            //Vamos empilar onde estamos
                            stackFrame.push(new State(atual.getName(), cp, k + 1));
                            atual = this.env.getProduction(alle[k].value());
                            cp = 0;
                            k = 0;
                            continue proc;
                            
                        case TOKEN:
                            
                            if (this.lt.hasNext()) {
                                
                                Entry gel = this.lt.getNext(1);
                                if (!gel.getToken().equalsIgnoreCase(alle[k].value())) {
                                    
                                    if (clauseP.isOptional()) {
                                        
                                        //this.lt.previous();
                                        if (!stackFrame.isEmpty()) {
            
                                            State top = stackFrame.pop();
                                            atual = this.env.getProduction(top.production);
                                            ac = atual.getClauses();
                                            cp = top.clause;
                                            k = top.entity;
                                        } else {

                                            cp = 0;
                                            k = 0;
                                        }
                                        continue proc;
                                    } else {
                                        
                                        error = true;
                                        errorMsg = "Error on line: " + gel.getLine()
                                                    + "\nExpecting " + alle[k].value()
                                                    + " but " + gel.getToken() + " found.";
                                        /*System.out.println("Error on line: " + gel.getLine());
                                        System.out.println("Expecting " + alle[k].value()
                                                + " but " + gel.getToken() + " found.");*/
                                        //TODO Desempilhar ou continuar?
                                        /*if (cp == ac.length - 1 && k == alle.length - 1) {
                                            
                                            System.out.println(errorMsg);
                                        }*/
                                    }
                                } else {
                                    
                                    this.lt.next();
                                }
                            } else {
                                
                                continue proc;
                            }
                            break;
                            
                        case TYPE:
                            
                            if (this.lt.hasNext()) {
                                
                                Entry gel = this.lt.getNext(1);
                                if (!gel.getDescription().equalsIgnoreCase(alle[k].value())) {
                                    
                                    if (clauseP.isOptional()) {
                                        
                                        this.lt.previous();
                                        if (!stackFrame.isEmpty()) {
            
                                            State top = stackFrame.pop();
                                            atual = this.env.getProduction(top.production);
                                            ac = atual.getClauses();
                                            cp = top.clause;
                                            k = top.entity;
                                        } else {

                                            cp = 0;
                                            k = 0;
                                        }
                                        continue proc;
                                    } else {
                                        
                                        error = true;
                                        errorMsg = "Error on line: " + gel.getLine()
                                                   + "\nExpecting " + alle[k].value()
                                                   + " but " + gel.getToken() + " found.";
                                        /*System.out.println("Error on line: " + gel.getLine());
                                        System.out.println("Expecting " + alle[k].value()
                                                + " but " + gel.getDescription() + " found.");
                                        //TODO Desempilhar ou continuar?*/
                                        /*if (cp == ac.length - 1 && k == alle.length - 1) {
                                            
                                            System.out.println(errorMsg);
                                        }*/
                                    }
                                } else {
                                    
                                    this.lt.next();
                                }
                            } else {
                                
                                continue proc;
                            }
                            break;
                            
                        case VOID:
                            
                            //I don't know what to do here yet.
                            System.out.println(atual.getName());
                            System.out.println("VOID");
                            break;
                    }
                    
                    if (error) {
                        
                        break;
                    }
                }
                
                /*if (!errorC) {
                
                    if (!stackFrame.isEmpty()) {

                        State top = stackFrame.pop();
                        atual = this.env.getProduction(top.production);
                        ac = atual.getClauses();
                        cp = top.clause;
                        k = top.entity;
                        ret_n_prod = true;
                    } else {

                        //System.out.println("NULL");
                        //atual = null;
                        cp = 0;
                        k = 0;
                    }
                    
                    continue proc;
                } else if(error && cp == ac.length - 1) {
                    
                    //System.out.println(errorMsg);
                    if (!stackFrame.isEmpty()) {
                        
                        stackFrame.peek().ret_n_prod = false;
                        ret_n_prod = false;
                    } else {
                        
                        ret_n_prod = false;
                        System.out.println("NULL");
                        atual = null;
                    }
                }*/
                
                if (error) {
                    
                    errorC = true;
                } else {
                    
                    k = 0;
                    //Processou todas as entidades sem dar erro
                    break;
                }
                
                k = 0;
            }
            
            //deve desempilhar aqui
            //Se processou todas as clausulas sem erro
            if (!errorC) {
                
                if (!stackFrame.isEmpty()) {

                    State top = stackFrame.pop();
                    atual = this.env.getProduction(top.production);
                    ac = atual.getClauses();
                    cp = top.clause;
                    k = top.entity;
                    ret_n_prod = true;
                } else {

                    //System.out.println("NULL");
                    //atual = null;
                    cp = 0;
                    k = 0;
                }

                continue proc;
            } else if(errorC && cp == ac.length - 1) {

                //System.out.println(errorMsg);
                if (!stackFrame.isEmpty()) {

                    stackFrame.peek().ret_n_prod = false;
                    ret_n_prod = false;
                } else {

                    ret_n_prod = false;
                    System.out.println("NULL");
                    atual = null;
                }
            }
            
            /*if (!stackFrame.isEmpty()) {
            
                //cp = this.getClauseToProcess(atual, lt);
            //} else {
                
                State top = stackFrame.pop();
                atual = this.env.getProduction(top.production);
                ac = atual.getClauses();
                cp = top.clause;
                k = top.entity;
            } else {

                System.out.println("NULL2");
                atual = null;
                cp = 0;
                k = 0;
            }*/
            
            /*if (this.lt.getCurrent() == null) {
                 break;
            }*/
            //System.out.println(lt.getCurrent().getToken());
        }
    }

    public SyntaticalTree getSyntaticalTree() {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    
    /*private int getClauseToProcess(Production p, LexicalTable lt) {
        
        //Procura se alguma começa com o token
        LexicalTable.Entry e = lt.getNext(1);
        
        Clause[] allC = p.getClauses();
        //If só temos uma clausa tentamos usá-la
        if (allC.length == 1) {
            
            if (allC[0].getEntities()[0].getType() == EntityType.PRODUCTION) {
                
                return 0;
            }
        }
        
        for (int c = 0; c < allC.length; c++) {
            
            Entity ge = allC[c].getEntities()[0];
            switch (ge.getType()) {
                
                case TOKEN:
                    
                    if (ge.value().equalsIgnoreCase(e.getToken())) {
                        
                        return c;
                    }
                    break;
                    
                case TYPE:
                    
                    if (ge.value().equalsIgnoreCase(e.getDescription())) {
                        
                        return c;
                    }
                    break;
            }
        }
        
        return -1;
    }*/
    
    private static final class State {
        
        public String production;
        
        public int clause;
        
        public int entity;
        
        public boolean ret_n_prod = true; //Não gerou erro na produção que chamou
        
        public State(String p, int c, int e) {
            
            this.production = p;
            this.clause = c;
            this.entity = e;
        }
    }
}
